/*
 * IM - Image I/O Library
 * Copyright (c) 1996 CENPES/Petrobras, TeCGraf/PUC-Rio.
 * Antonio E. Scuri - scuri@tecgraf.puc-rio.br
 * 
 * IMIO.C
 * 
 * Contains the implementation of the generic functions 
 * that calls each's format specific functions.
 *
 * History:
 *          1996.04.05 - Changed the structure of the module to suport more filename formats.
 *          1996.06.14 - Finished inicial version.
 *
 */

#include "im.h"
#include "imio.h"


#include <stdlib.h>



static imFormat* imFormatList[50];
static int imFormatCount = 0;

/* this function is called once before each format IO function call */
/* It initializes the fle format list */
/* implemented in the im.c module */
void imInitFormatList(void);


/* used only in the im.c module and here */
void imAddFormatInit(imFormat* iformat)
{
  imFormatList[imFormatCount] = iformat;
  iformat->format = imFormatCount;
  imFormatCount++;
}


int imAddFormat(imFormat* iformat)
{
  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  imAddFormatInit(iformat);

  imFormatList[imFormatCount] = NULL;
  return (imFormatCount - 1);
}


static imCounterCallback imCounterCB = NULL;
imResolutionCallback imResolutionCB = NULL;


int imRegisterCallback(imCallback cb, int cb_id, int format)
{
  if (format != -1)
  {
    imFormat* iformat;

    /* If the format list is not initialized yet do it. */
    if (imFormatCount == 0)
      imInitFormatList();

    /* take the format from the list */
    iformat = imFormatList[(unsigned char)format];

    return iformat->RegisterCallback(cb, cb_id);
  }

  switch(cb_id)
  {
  case IM_COUNTER_CB:
    {
      imCounterCB = (imCounterCallback)cb;
      /* callback found and registered. */
      return 1;
    }
  case IM_RESOLUTION_CB:
    {
      imResolutionCB = (imResolutionCallback)cb;
      /* callback found and registered. */
      return 1;
    }
  }

  /* callback not found */
  return 0;
}


int imFileFormat(char *filename, int* format)
{
  int f = 0;

  if (!format)
    return IM_ERR_FORMAT;

  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  /* Loop for all registered formats */
  for (f = 0; f < imFormatCount; f++)
  {
    int err;

    /* take one format from the list */
    imFormat* iformat = imFormatList[f];

    /* opens the file for reading also reads the file headers. */
    imFile* ifile = iformat->ReadOpen(filename, &err);
    if (err == IM_ERR_NONE || err == IM_ERR_TYPE || err == IM_ERR_COMP)
    {
      /* the format identifier is equal to the format list index */
      /* adds compression information to the format descriptor on the second byte. */
      *format = f | ifile->compress;

      /* closes the file */
      iformat->Close(ifile);

      /* the format for that file was found and information was sucessfully retreived. */
      return IM_ERR_NONE;
    }

    /* if the error was opening or reading abort */
    if (err != IM_ERR_FORMAT)
      return err;
  }

  /* did not find any suitable format for that file */
  return IM_ERR_FORMAT;
}/* imFileFormat */


int imImageInfo(char *filename, int *width, int *height, int *type, int *pal_size)
{
  int f = 0;

  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  /* Loop for all registered formats. */
  for (f = 0; f < imFormatCount; f++)
  {
    int err;

    /* take one format from the list */
    imFormat* iformat = imFormatList[f];

    /* opens the file for reading also reads the file headers. */
    imFile* ifile = iformat->ReadOpen(filename, &err);
    if (err == IM_ERR_NONE || err == IM_ERR_TYPE || err == IM_ERR_COMP)
    {
      /* retreives image information */
      if (width)    *width    = ifile->width;
      if (height)   *height   = ifile->height;
      if (type)     *type     = ifile->type;
      if (pal_size) *pal_size = ifile->pal_size;

      /* closes the file */
      iformat->Close(ifile);

      /* the format for that file was found and information was sucessfully retreived. */
      return IM_ERR_NONE;
    }

    /* if the error was opening or reading abort */
    if (err != IM_ERR_FORMAT)
      return err;
  }

  /* did not find any suitable format for that file */
  return IM_ERR_FORMAT;
}/* imImageInfo */


int imLoadRGB(char *filename, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int f = 0, y, err;
  imFormat* iformat;
  imFile* ifile;
  unsigned char *red_line, *green_line, *blue_line;

  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  /* Loop for all registered formats. */
  for (f = 0; f < imFormatCount; f++)
  {
    /* take one format from the list */
    iformat = imFormatList[f];

    /* opens the file for reading also reads the file headers. */
    ifile = iformat->ReadOpen(filename, &err);

    /* if we found the format break the loop. */
    if (err == IM_ERR_NONE)
      break;

    /* closes the file */
    if (ifile) iformat->Close(ifile);

    /* if the error was opening or reading abort */
    if (err != IM_ERR_FORMAT)
      return err;
  }

  /* if did not find any suitable format for that file return the error code. */
  if (err == IM_ERR_FORMAT)
    return IM_ERR_FORMAT;

  /* starts the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 0, IM_READ);

  /* if the image is a map image we have to convert it to RGB */
  if (ifile->type == IM_MAP)
  {
    int c, x, index;
    unsigned char *map_line;

    /* tables to speed up convertion */
    unsigned char r[256], g[256], b[256];

    /* alocates memory for the color map  */
    long* colors = (long*) malloc(sizeof(long)*ifile->pal_size);

    /* reads the color map */
    err = iformat->ReadColors(ifile, colors);
    if (err)
    {
      /* closes the file */
      iformat->Close(ifile);

      /* release color map memory */
      free(colors);

      /* informs that a read error ocurred */
      return IM_ERR_READ;
    }

    /* Creates a convertion table to speed up the convertion from MAP to RGB. */
    for (c = 0; c < ifile->pal_size; c++)
      imDecodeColor(&r[c], &g[c], &b[c], colors[c]);

    /* release color map memory */
    free(colors);

    /* allocates memory for the map buffer */
    map_line = (unsigned char*) malloc(ifile->width);

    /* loop for all image lines */
    for (y = 0; y < ifile->height; y++)
    {
      /* moves to the next image line */
      red_line = red + ifile->width * ifile->cur_line;
      green_line = green + ifile->width * ifile->cur_line;
      blue_line = blue + ifile->width * ifile->cur_line;

      /* reads the image line already decompressed and converted to our format */
      err = iformat->ReadLine(ifile, map_line);
      if (err)
      {
        /* closes the file */
        iformat->Close(ifile);

        /* release map buffer memory */
        free(map_line);

        /* informs that a read error ocurred */
        return IM_ERR_READ;
      }

      /* transfer the data from the map buffer to the RGB buffer using the tables to convert the colors. */
      for (x = 0; x < ifile->width; x++)
      {
        index = map_line[x];
        red_line[x] = r[index];
        green_line[x] = g[index];
        blue_line[x] = b[index];
      }

      /* increments the counter if it exists */
      if (imCounterCB) 
      {
        int esc = imCounterCB(filename, (100 * y) / ifile->height, IM_READ);

        /* if the callback aborts the process we have to interrupt */
        if (esc)
        {
          /* closes the file */
          iformat->Close(ifile);

          /* release map buffer memory */
          free(map_line);

          /* informs that we interrupted. */
          return IM_INTERRUPTED;
        }
      }
    }

    /* release map buffer memory */
    free(map_line);
  }
  else
  {
    /* if the image is splited in several planes we must read it one after another */
    if (ifile->split)
    {
      int p;
      unsigned char* planes[3];
      unsigned char *plane;
      unsigned char *plane_line;

      /* initialize our plane order */
      planes[0] = red;
      planes[1] = green;
      planes[2] = blue;

      /* loop for all image planes */
      for(p = 0; p < 3; p++)
      {
        /* the planes order is format dependent so we must read using the format order. */
        plane = planes[ifile->planes[p]];

        /* loop for all image lines */
        for (y = 0; y < ifile->height; y++)
        {
          /* moves to the next image line, we expects that for each plane cur_line is restarted */
          plane_line = plane + ifile->width * ifile->cur_line;

          /* reads the image line already decompressed and converted to our format */
          err = iformat->ReadLine(ifile, plane_line);
          if (err)
          {
            /* closes the file */
            iformat->Close(ifile);

            /* informs that a read error ocurred */
            return IM_ERR_READ;
          }

          /* increments the counter if it exists */
          if (imCounterCB) 
          {
            int esc = imCounterCB(filename, (100 * (y + p * ifile->height)) / (ifile->height * 3), IM_READ);

            /* if the callback aborts the process we have to interrupt */
            if (esc)
            {
              /* closes the file */
              iformat->Close(ifile);

              /* informs that we interrupted. */
              return IM_INTERRUPTED;
            }
          }
        }
      }
    }
    else
    {
      /* loop for all image lines */
      for (y = 0; y < ifile->height; y++)
      {
        /* moves to the next image line */
        red_line = red + ifile->width * ifile->cur_line;
        green_line = green + ifile->width * ifile->cur_line;
        blue_line = blue + ifile->width * ifile->cur_line;

        /* reads the image line already decompressed and converted to our format */
        err = iformat->ReadLineRGB(ifile, red_line, green_line, blue_line);
        if (err)
        {
          /* closes the file */
          iformat->Close(ifile);

          /* informs that a read error ocurred */
          return IM_ERR_READ;
        }

        /* increments the counter if it exists */
        if (imCounterCB) 
        {
          int esc = imCounterCB(filename, (100 * y) / ifile->height, IM_READ);

          /* if the callback aborts the process we have to interrupt */
          if (esc)
          {
            /* closes the file */
            iformat->Close(ifile);

            /* informs that we interrupted. */
            return IM_INTERRUPTED;
          }
        }
      }
    }
  }

  /* ends the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 100, IM_READ);

  /* closes the file */
  iformat->Close(ifile);

  /* the format for that file was found and information was sucessfully retreived. */
  return IM_ERR_NONE;
}/* imLoadRGB */


int imLoadMap(char *filename, unsigned char *map, long *colors)
{
  int f = 0, y, err;
  imFormat* iformat;
  imFile* ifile;
  unsigned char *map_line;

  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  /* Loop for all registered formats. */
  for (f = 0; f < imFormatCount; f++)
  {
    /* take one format from the list */
    iformat = imFormatList[f];

    /* opens the file for reading also reads the file headers. */
    ifile = iformat->ReadOpen(filename, &err);

    /* if we found the format break the loop. */
    if (err == IM_ERR_NONE)
      break;

    /* closes the file */
    if (ifile) iformat->Close(ifile);

    /* if the error was opening or reading abort */
    if (err != IM_ERR_FORMAT)
      return err;
  }

  /* if did not find any suitable format for that file return the error code. */
  if (err == IM_ERR_FORMAT)
    return IM_ERR_FORMAT;

  /* if the image is RGB we can not read as map */
  if (ifile->type == IM_RGB)
    return IM_ERR_TYPE;

  /* starts the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 0, IM_READ);

  /* reads the color map */
  err = iformat->ReadColors(ifile, colors);
  if (err)
  {
    /* closes the file */
    iformat->Close(ifile);

    /* informs that a read error ocurred */
    return IM_ERR_READ;
  }

  /* loop for all image lines */
  for (y = 0; y < ifile->height; y++)
  {
    /* moves to the next image line */
    map_line = map + ifile->width * ifile->cur_line;

    /* reads the image line already decompressed and converted to our format */
    err = iformat->ReadLine(ifile, map_line);
    if (err)
    {
      /* closes the file */
      iformat->Close(ifile);

      /* informs that a read error ocurred */
      return IM_ERR_READ;
    }

    /* increments the counter if it exists */
    if (imCounterCB) 
    {
      int esc = imCounterCB(filename, (100 * y) / ifile->height, IM_READ);

      /* if the callback aborts the process we have to interrupt */
      if (esc)
      {
        /* closes the file */
        iformat->Close(ifile);

        /* informs that we interrupted. */
        return IM_INTERRUPTED;
      }
    }
  }

  /* ends the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 100, IM_READ);

  /* closes the file */
  iformat->Close(ifile);

  /* the format for that file was found and information was sucessfully retreived. */
  return IM_ERR_NONE;
}/* imLoadMap */


int imSaveRGB(int width, int height, int format, unsigned char *red, unsigned char *green, unsigned char *blue, char *filename)
{
  int err, y;
  imFormat* iformat;
  imFile* ifile;
  unsigned char *red_line, *green_line, *blue_line;

  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  /* take the format from the list */
  iformat = imFormatList[(unsigned char)format];

  /* opens the file for writing, also writes the file headers. */
  ifile = iformat->WriteOpen(filename, width, height, IM_RGB, format & 0xFFFF00, 0, &err);
  if (err != IM_ERR_NONE)
  {
    /* closes the file */
    if (ifile) iformat->Close(ifile);

    return err;
  }

  /* starts the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 0, IM_WRITE);

  /* if the image is splited in several planes we must read it one after another */
  if (ifile->split)
  {
    int p;
    unsigned char* planes[3];
    unsigned char *plane;
    unsigned char *plane_line;

    /* initialize our plane order */
    planes[0] = red;
    planes[1] = green;
    planes[2] = blue;

    /* loop for all image planes */
    for(p = 0; p < 3; p++)
    {
      /* the planes order is format dependent so we must read using the format order. */
      plane = planes[ifile->planes[p]];

      /* loop for all image lines */
      for (y = 0; y < ifile->height; y++)
      {
        /* moves to the next image line, we expects that for each plane cur_line is restarted */
        plane_line = plane + ifile->width * ifile->cur_line;

        /* writes the image line */
        err = iformat->WriteLine(ifile, plane_line);
        if (err)
        {
          /* closes the file */
          iformat->Close(ifile);

          /* informs that a write error ocurred */
          return IM_ERR_WRITE;
        }

        /* increments the counter if it exists */
        if (imCounterCB) 
        {
          int esc = imCounterCB(filename, (100 * (y + p * ifile->height)) / (ifile->height * 3), IM_WRITE);

          /* if the callback aborts the process we have to interrupt */
          if (esc)
          {
            /* closes the file */
            iformat->Close(ifile);

            /* informs that we interrupted. */
            return IM_INTERRUPTED;
          }
        }
      }
    }
  }
  else
  {
    /* loop for all image lines */
    for (y = 0; y < ifile->height; y++)
    {
      /* moves to the next image line */
      red_line = red + ifile->width * ifile->cur_line;
      green_line = green + ifile->width * ifile->cur_line;
      blue_line = blue + ifile->width * ifile->cur_line;

      /* writes the image line */
      err = iformat->WriteLineRGB(ifile, red_line, green_line, blue_line);
      if (err)
      {
        /* closes the file */
        iformat->Close(ifile);

        /* informs that a write error ocurred */
        return IM_ERR_WRITE;
      }

      /* increments the counter if it exists */
      if (imCounterCB) 
      {
        int esc = imCounterCB(filename, (100 * y) / ifile->height, IM_WRITE);

        /* if the callback aborts the process we have to interrupt */
        if (esc)
        {
          /* closes the file */
          iformat->Close(ifile);

          /* informs that we interrupted. */
          return IM_INTERRUPTED;
        }
      }
    }
  }

  /* ends the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 100, IM_WRITE);

  /* closes the file */
  iformat->Close(ifile);

  /* informs that everything was OK. */
  return IM_ERR_NONE;
}/* imSaveRGB */


int imSaveMap(int width, int height, int format, unsigned char *map, int pal_size, long *colors, char * filename)
{
  int err, y;
  imFormat* iformat;
  imFile* ifile;
  unsigned char *map_line;

  /* If the format list is not initialized yet do it. */
  if (imFormatCount == 0)
    imInitFormatList();

  /* take the format from the list */
  iformat = imFormatList[(unsigned char)format];

  /* opens the file for writing, also writes the file headers. */
  ifile = iformat->WriteOpen(filename, width, height, IM_MAP, format & 0xFFFF00, pal_size, &err);
  if (err != IM_ERR_NONE)
  {
    /* closes the file */
    if (ifile) iformat->Close(ifile);

    return err;
  }

  /* starts the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 0, IM_WRITE);

  /* writes the color map if it is before the image */
  if (ifile->pre_colors)
  {
    err = iformat->WriteColors(ifile, colors);
    if (err)
    {
      /* closes the file */
      iformat->Close(ifile);

      /* informs that a write error ocurred */
      return IM_ERR_WRITE;
    }
  }

  /* loop for all image lines */
  for (y = 0; y < ifile->height; y++)
  {
    /* moves to the next image line */
    map_line = map + ifile->width * ifile->cur_line;

    /* writes the image line */
    err = iformat->WriteLine(ifile, map_line);
    if (err)
    {
      /* closes the file */
      iformat->Close(ifile);

      /* informs that a write error ocurred */
      return IM_ERR_WRITE;
    }

    /* increments the counter if it exists */
    if (imCounterCB) 
    {
      int esc = imCounterCB(filename, (100 * y) / ifile->height, IM_WRITE);

      /* if the callback aborts the process we have to interrupt */
      if (esc)
      {
        /* closes the file */
        iformat->Close(ifile);

        /* informs that we interrupted. */
        return IM_INTERRUPTED;
      }
    }
  }

  /* writes the color map if it is after the image */
  if (!ifile->pre_colors)
  {
    err = iformat->WriteColors(ifile, colors);
    if (err)
    {
      /* closes the file */
      iformat->Close(ifile);

      /* informs that a write error ocurred */
      return IM_ERR_WRITE;
    }
  }

  /* ends the counter if it exists */
  if (imCounterCB) imCounterCB(filename, 100, IM_WRITE);

  /* closes the file */
  iformat->Close(ifile);

  /* informs that everything was OK. */
  return IM_ERR_NONE;
}/* imSaveMap */


